/* ****************************************************************************
 *
 * Copyright (c) Microsoft Corporation. 
 *
 * This source code is subject to terms and conditions of the Microsoft Public License. A
 * copy of the license can be found at http://go.microsoft.com/fwlink/?LinkID=131993. If
 * you cannot locate the Microsoft Public License, please send an email to
 * mixon@microsoft.com. By using this source code in any fashion, you are agreeing to
 * be bound by the terms of the Microsoft Public License.
 *
 * You must not remove this notice, or any other, from this software.
 *
 *
 * ***************************************************************************/

using System;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Media;

namespace MIXOnline.Descry
{
    public class TreeMapPanel : Panel
    {
        public static readonly DependencyProperty AreaProperty =
            DependencyProperty.RegisterAttached(
                "Area",
                typeof(double),
                typeof(TreeMapPanel),
                null
                    );
        public static double GetArea(DependencyObject element)
        {
            if (element == null) throw new NullReferenceException(element.ToString());
            return (double)element.GetValue(AreaProperty);
        }
        public static void SetArea(DependencyObject element, double value)
        {
            if (element == null) throw new NullReferenceException(element.ToString());
            element.SetValue(AreaProperty, value);
        }
        public double Area
        {
            get { return (double)GetValue(AreaProperty); }
            set
            {
                SetValue(AreaProperty, value);
            }
        }

        //added color property by Hanshu 4/21/09
        private Color _Color;
        public Color Color
        {
            get { return _Color; }
            set
            {
                this.Background = new SolidColorBrush(value);
                _Color = value;
            }
        }

        public static readonly DependencyProperty LabelProperty =
            DependencyProperty.RegisterAttached(
                "Label",
                typeof(string),
                typeof(TreeMapPanel),
                null
                );

        public static string GetLabel(DependencyObject element)
        {
            if (element == null) throw new NullReferenceException(element.ToString());
            return (string)element.GetValue(LabelProperty);
        }
        public static void SetLabel(DependencyObject element, string value)
        {
            if (element == null) throw new NullReferenceException(element.ToString());
            element.SetValue(LabelProperty, value);
        }
        public string Label
        {
            get { return (string)GetValue(LabelProperty); }
            set
            {
                SetValue(LabelProperty, value);
            }
        }

        private SortMethod _sort = SortMethod.LargestToSmallest;

        public SortMethod Sort
        {
            get { return _sort; }
            set { _sort = value; }
        }

        public enum SortMethod
        {
            LargestToSmallest,
            NoSort,
        }

        protected override Size ArrangeOverride(Size finalSize)
        {
            if (finalSize.Width < c_tolerance || finalSize.Height < c_tolerance)
            {
                return finalSize;
            }

            UIElementCollection children = this.Children;
            ComputeWeightMap(children);

            Rect strip = new Rect(new Point(0, 0), new Point(finalSize.Width, finalSize.Height));

            double remainingWeight = m_totalWeight;

            int arranged = 0;
            while (arranged < children.Count)
            {
                double bestStripWeight = 0;
                double bestRatio = double.PositiveInfinity;

                int i;

                if (finalSize.Width < c_tolerance || finalSize.Height < c_tolerance)
                {
                    return finalSize;
                }

                if (strip.Width > strip.Height)
                {
                    double bestWidth = strip.Width;

                    // Arrange Vertically
                    for (i = arranged; i < children.Count; i++)
                    {
                        double stripWeight = bestStripWeight + GetWeight(i);
                        double ratio = double.PositiveInfinity;
                        double width = strip.Width * stripWeight / remainingWeight;

                        for (int j = arranged; j <= i; j++)
                        {
                            double height = strip.Height * GetWeight(j) / stripWeight;
                            ratio = Math.Min(ratio, height > width ? height / width : width / height);

                            if (ratio > bestRatio)
                            {
                                goto ArrangeVertical;
                            }
                        }
                        bestRatio = ratio;
                        bestWidth = Math.Round(width, 0);
                        bestStripWeight = stripWeight;
                    }

                ArrangeVertical:
                    double y = Math.Round(strip.Y, 0);
                    for (; arranged < i; arranged++)
                    {
                        UIElement child = GetChild(children, arranged);

                        double height = Math.Round(strip.Height * GetWeight(arranged) / bestStripWeight, 0);
                        child.Arrange(new Rect(strip.X, y, bestWidth, height));
                        y += height;
                    }

                    strip.X = strip.X + bestWidth;
                    strip.Width = Math.Max(0.0, strip.Width - bestWidth);
                }
                else
                {
                    double bestHeight = strip.Height;

                    // Arrange Horizontally
                    for (i = arranged; i < children.Count; i++)
                    {
                        double stripWeight = bestStripWeight + GetWeight(i);
                        double ratio = double.PositiveInfinity;
                        double height = Math.Round(strip.Height * stripWeight / remainingWeight, 0);

                        for (int j = arranged; j <= i; j++)
                        {
                            double width = strip.Width * GetWeight(j) / stripWeight;
                            ratio = Math.Min(ratio, height > width ? height / width : width / height);

                            if (ratio > bestRatio)
                            {
                                goto ArrangeHorizontal;
                            }
                        }
                        bestRatio = ratio;
                        bestHeight = Math.Round(height, 0);
                        bestStripWeight = stripWeight;
                    }

                ArrangeHorizontal:
                    double x = Math.Round(strip.X, 0);
                    for (; arranged < i; arranged++)
                    {
                        UIElement child = GetChild(children, arranged);

                        double width = Math.Round(strip.Width * GetWeight(arranged) / bestStripWeight, 0);
                        child.Arrange(new Rect(x, strip.Y, width, bestHeight));
                        x += width;
                    }

                    strip.Y = strip.Y + bestHeight;
                    strip.Height = Math.Max(0.0, strip.Height - bestHeight);
                }
                remainingWeight -= bestStripWeight;
            }

            return finalSize;
        }

        private UIElement GetChild(UIElementCollection children, int index)
        {
            return children[m_weightMap[index]];
        }

        private double GetWeight(int index)
        {
            return m_weights[m_weightMap[index]];
        }

        private void ComputeWeightMap(UIElementCollection children)
        {

            m_totalWeight = 0;

            if (m_weightMap == null || m_weightMap.Length != this.Children.Count)
            {
                m_weightMap = new int[this.Children.Count];
            }

            if (m_weights == null || m_weights.Length != this.Children.Count)
            {
                m_weights = new double[this.Children.Count];
            }

            for (int i = 0; i < m_weightMap.Length; i++)
            {
                m_weightMap[i] = i;
                m_weights[i] = GetArea(children[i]);
                m_totalWeight += m_weights[i];
            }

            if (_sort == SortMethod.LargestToSmallest)
                Array.Sort<int>(m_weightMap, compareWeights);
        }

        private int compareWeights(int index1, int index2)
        {
            return m_weights[index2].CompareTo(m_weights[index1]);
        }

        private double m_totalWeight;
        private int[] m_weightMap;
        private double[] m_weights;

        private const double c_tolerance = 1e-2;

    }
}